import json
import os

from flask import Flask

from secure_scaffold import config
from secure_scaffold import xsrf


class AppFactory:
    """
    Factory to generate a Flask app that includes the security config
    """

    def __init__(self, *args, name=None, **kwargs):
        if not name:
            name = self.get_name()
        self.name = name
        self.args = args
        self.kwargs = kwargs

    def get_name(self) -> str:
        """
        Get the name for the Flask Application.

        This should generally reflect the top level name of
        your application as stated here: http://flask.pocoo.org/docs/1.0/api/

        :return: The name of the application.
        :rtype: str
        """
        return os.path.split(os.getcwd())[-1]

    def setup_app_config(self, app: Flask) -> Flask:
        """
        Setup the configuration for the Flask app.

        This method is meant to be overridden in the case
        that a Flask app needs extra configuration.

        By default it sets the app Secret Key.

        :param Flask app: The Flask app that requires configuring.
        :return: The configured Flask app.
        :rtype: Flask
        """
        app.secret_key = config.get_setting('SECRET_KEY')
        return app

    @staticmethod
    def add_report_to_headers(response):
        """
        Generate the Report-To header to be added to a response.

        :param response: The response our app has generated which requires headers.
        :return: The response with the required headers.
        """

        response.headers['Report-To'] = json.dumps(
            config.get_setting('REPORT_TO_HEADER')
        )
        return response

    @staticmethod
    def add_csp_headers(response):
        """
        Generate CSP Headers to be added to a response.

        The CSP headers are generated by inspecting the CSP_CONFIG object
        in the settings module.

        :param response: The response our app has generated which requires headers.
        :return: The response with the required headers.
        """

        csp_headers = '; '.join(
            f'{key} {value}'
            for key, value in config.get_setting('CSP_CONFIG').items()
        )
        response.headers['Content-Security-Policy'] = csp_headers

        return response

    def add_app_headers(self, app: Flask) -> Flask:
        """
        Add app specific headers to every response.

        By default we always want CSP headers for an App which by default
        this method will add.

        This method is easily extendable in the case more headers needed to be
        added in any other way.

        :param Flask app: The Flask app that requires the added headers.
        :return: The Flask app with the added headers.
        :rtype: Flask
        """
        app.after_request(self.add_report_to_headers)
        app.after_request(self.add_csp_headers)
        return app

    def add_xsrf_error_handler(self, app: Flask) -> Flask:
        """
        Add the xsrf error handler to the app.

        We want xsrf to return verbose error messages and for this we need to
        attach a handler to the app to return the error response correctly.

        :param app: The Flask app to add the handler to.
        :return: The Flask app now with error handler.
        """
        app.register_error_handler(xsrf.XSRFError, xsrf.handle_xsrf_error)
        return app

    def generate(self) -> Flask:
        """
        Generate a Flask application with our preferred defaults.

        :return: A Flask Application with our preferred defaults.
        :rtype: Flask
        """
        app = Flask(self.name, *self.args, **self.kwargs)
        app = self.setup_app_config(app)
        app = self.add_app_headers(app)
        app = self.add_xsrf_error_handler(app)

        return app
